package org.insightech.er.db;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.ui.PlatformUI;
import org.insightech.er.ERDiagramActivator;
import org.insightech.er.editor.model.settings.JDBCDriverSetting;
import org.insightech.er.preference.PreferenceInitializer;
import org.insightech.er.preference.page.jdbc.JDBCPathDialog;

public abstract class DBManagerBase implements DBManager {

    private Set<String> reservedWords = new HashSet<String>();

    private final Map<String, ClassLoader> loaderMap;

    public DBManagerBase() {
        DBManagerFactory.addDB(this);

        reservedWords = getReservedWords();

        loaderMap = new HashMap<String, ClassLoader>();
    }

    @Override
    public String getURL(final String serverName, final String dbName, final int port) {
        // String temp = serverName.replaceAll("\\\\", "\\\\\\\\");
        String url = this.getURL().replaceAll("<SERVER NAME>", Matcher.quoteReplacement(serverName));
        url = url.replaceAll("<PORT>", String.valueOf(port));

        // temp = dbName.replaceAll("\\\\", "\\\\\\\\");
        url = url.replaceAll("<DB NAME>", Matcher.quoteReplacement(dbName));

        return url;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<Driver> getDriverClass(final String driverClassName) {
        String path = null;
        Class clazz = null;

        try {
            if (driverClassName.equals("sun.jdbc.odbc.JdbcOdbcDriver")) {
                return (Class<Driver>) Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

            } else {
                path = PreferenceInitializer.getJDBCDriverPath(getId(), driverClassName);

                // Cache the class loader to map.
                // Because if I use the another loader with the driver using
                // native library(.dll)
                // next error occur.
                //
                // java.lang.UnsatisfiedLinkError: Native Library xxx.dll
                // already loaded in another classloader
                //
                ClassLoader loader = loaderMap.get(path);
                if (loader == null) {
                    loader = getClassLoader(path);
                    loaderMap.put(path, loader);
                }

                clazz = loader.loadClass(driverClassName);
            }

        } catch (final Exception e) {
            final JDBCPathDialog dialog = new JDBCPathDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), getId(), driverClassName, path, new ArrayList<JDBCDriverSetting>(), false);

            if (dialog.open() == IDialogConstants.OK_ID) {
                final JDBCDriverSetting newDriverSetting = new JDBCDriverSetting(getId(), dialog.getDriverClassName(), dialog.getPath());

                final List<JDBCDriverSetting> driverSettingList = PreferenceInitializer.getJDBCDriverSettingList();

                if (driverSettingList.contains(newDriverSetting)) {
                    driverSettingList.remove(newDriverSetting);
                }
                driverSettingList.add(newDriverSetting);

                PreferenceInitializer.saveJDBCDriverSettingList(driverSettingList);

                clazz = getDriverClass(dialog.getDriverClassName());
            }
        }

        return clazz;
    }

    private ClassLoader getClassLoader(final String uri) throws SQLException, MalformedURLException {

        final StringTokenizer tokenizer = new StringTokenizer(uri, ";");
        final int count = tokenizer.countTokens();

        final URL[] urls = new URL[count];

        for (int i = 0; i < urls.length; i++) {
            urls[i] = new URL("file", "", tokenizer.nextToken());
        }

        final URLClassLoader loader = new URLClassLoader(urls, ERDiagramActivator.getClassLoader());

        return loader;
    }

    abstract protected String getURL();

    @Override
    abstract public String getDriverClassName();

    protected Set<String> getReservedWords() {
        final Set<String> reservedWords = new HashSet<String>();

        final ResourceBundle bundle = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".reserved_word");

        final Enumeration<String> keys = bundle.getKeys();

        while (keys.hasMoreElements()) {
            reservedWords.add(keys.nextElement().toUpperCase());
        }

        return reservedWords;
    }

    @Override
    public boolean isReservedWord(final String str) {
        return reservedWords.contains(str.toUpperCase());
    }

    @Override
    public boolean isSupported(final int supportItem) {
        final int[] supportItems = getSupportItems();

        for (int i = 0; i < supportItems.length; i++) {
            if (supportItems[i] == supportItem) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean doesNeedURLDatabaseName() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean doesNeedURLServerName() {
        return true;
    }

    abstract protected int[] getSupportItems();

    @Override
    public List<String> getImportSchemaList(final Connection con) throws SQLException {
        final List<String> schemaList = new ArrayList<String>();

        final DatabaseMetaData metaData = con.getMetaData();
        try {
            final ResultSet rs = metaData.getSchemas();

            while (rs.next()) {
                schemaList.add(rs.getString(1));
            }

        } catch (final SQLException e) {
            // when schema is not supported
        }

        return schemaList;
    }

    @Override
    public List<String> getSystemSchemaList() {
        final List<String> list = new ArrayList<String>();

        return list;
    }

    @Override
    public List<String> getForeignKeyRuleList() {
        final List<String> list = new ArrayList<String>();

        list.add("RESTRICT");
        list.add("CASCADE");
        list.add("NO ACTION");
        list.add("SET NULL");
        list.add("SET DEFAULT");

        return list;
    }
}
